package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import jodd.util.StringUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;

import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.swing.*;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Administrator
 * @create 2020-05-21 9:33
 */
@Service
public class ListServiceImpl implements ListService {

    @Autowired
    private GoodsDao goodsDao;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;

    //上架添加索引
    @Override
    public void upperGoods(Long skuId) {
        Goods goods = new Goods();
        //1.根据skuId查询该商品的信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuId);
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setTitle(skuInfo.getSkuName());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        //2.添加品牌信息
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
        goods.setTmId(baseTrademark.getId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());
        //3.添加一二三级属性
        BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(categoryView.getCategory1Id());
        goods.setCategory1Name(categoryView.getCategory1Name());
        goods.setCategory2Id(categoryView.getCategory2Id());
        goods.setCategory2Name(categoryView.getCategory2Name());
        goods.setCategory3Id(categoryView.getCategory3Id());
        goods.setCategory3Name(categoryView.getCategory3Name());
        //4.添加平台属性
        List<SkuAttrValue> attrList = productFeignClient.getAttrList(skuId);
        List<SearchAttr> searchAttrList = attrList.stream().map(skuAttrValue -> {
            SearchAttr searchAttr = new SearchAttr();
            //平台属性ID
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            //平台属性名称
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            //平台属性值名称
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(searchAttrList);
        goods.setCreateTime(new Date());
        //保存索引
        goodsDao.save(goods);

    }

    //下架删除索引
    @Override
    public void lowerGoods(Long skuId) {
        goodsDao.deleteById(skuId);
    }
    //访问时更新热度
    @Override
    public void incrHotScore(Long skuId) {
        /*Zset类型 (有序集合类型)
        redisTemplate.opsForZSet().add("小明","语文",88);
        redisTemplate.opsForZSet().add("小明","数学",98);
        redisTemplate.opsForZSet().add("小明","英语",78);*/
        /*
        Goods没有更新，添加时如果有就是更新。
        它的底层是将原来的删除后再添加
         */
        String hotScore = "hotScore";
        Double score = redisTemplate.opsForZSet().incrementScore(hotScore, skuId, 1);//incrementScore追加分数  1
        System.out.println("当前分数：" + score);
        /*
        为加一分就操作Goods库3次，性能不高，再者热度不需要实时性
        10分更新一次索引库 10 20 30
         */
        if(score%10 == 0){
            //1.根据shuId查询Goods库数据
            Optional<Goods> optional = goodsDao.findById(skuId);
            Goods goods = optional.get();
            //更新热度
            //goods.setHotScore(goods.getHotScore()+1);
            goods.setHotScore(Math.round(score));//追加
            //3.更新索引库
            goodsDao.save(goods);
        }
    }
//*****************************************************************************************8
    @Autowired
    private RestHighLevelClient restHighLevelClient;//ES的原生Api客户端
        //开始搜素功能
    @Override
    public SearchResponseVo list(SearchParam searchParam) {
        //1.构建搜索的SearchRequest条件对象
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        try {
            //2.搜索
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //3.构建返回值SearchResponseVo对象 解析结果
            SearchResponseVo searchResponseVo = parseSearchResponse(searchResponse);
            //3.1 总条数
            //3.2 当前页 和 每页数
            searchResponseVo.setPageNo(searchParam.getPageNo());
            searchResponseVo.setPageSize(searchParam.getPageSize());
            //3.3 总页数=(总条数+每页数-1)/每页数
            Long totalPages = (searchResponseVo.getTotal()+searchResponseVo.getPageSize()-1)/searchResponseVo.getPageSize();
            searchResponseVo.setTotalPages(totalPages);
            return searchResponseVo;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 构建搜索的SearchRequest条件对象  入参6部分数据
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        //构建条件资源对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //构建组合条件对象（为了保存多条件）1级
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //1.关键词(搜索字段)
        //searchSourceBuilder.query(QueryBuilders.matchAllQuery());//查询所有 没有条件
        //1.0判断关键词是否有值
        String keyword = searchParam.getKeyword();
        if(!StringUtils.isEmpty(keyword)){
            //searchSourceBuilder.query(QueryBuilders.matchQuery("title",keyword));模糊查询
            //不能多次调用，前面的会被后面的替换  //无法形成追加条件的感觉,所以要
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword).operator(Operator.AND));
            //.operator(Operator.AND)多个关键词是取交集，默认是或的关系
            //must必须要(所以的结果 ==and(sal语句中的))，should相当于sql中的or的关系
        }
        //2:一二三级分类的ID
        Long category1Id = searchParam.getCategory1Id();
        if(null != category1Id){  //filter是前面用的什么就是什么(must) 这里可以不用再用must。termQuery精准，等价于sql中的“=”
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if(null != category1Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if(null != category3Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",category3Id));
        }

        //3:品牌e Id:名称
        String trademark = searchParam.getTrademark();
        if(!StringUtils.isEmpty(trademark)){
            String[] split = StringUtils.split(trademark, ":");//以 : 切割，该方法只能切割一次
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
        }
        //4:平台属性 平台属性ID:平台属性值名称:平台属性名称
        //CollectionUtils.isEmpty()判断集合是否为空
        String[] props = searchParam.getProps();
        if(null != props && props.length>0){//有值
            for (String prop : props){
                //StringUtils.split();只能切割一个：
                String[] p = prop.split(":");
                //子组合条件对象 2级
                BoolQueryBuilder subBoolQueryBuilder = QueryBuilders.boolQuery();
                subBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId",p[0]));//路径加属性attrs.attrId
                subBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrValue",p[1]));
            //将子组合对象设置给父组合对象  nestedQuery表示嵌套。   参数1：路径,参数2：设置的子组合条件对象
                // 参数3：模式  本次的None  "attrs" : [对象（三个属性），2，3，4，5]
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs",subBoolQueryBuilder, ScoreMode.None));
            }
        }
        //给条件资源对象设置组合条件集合
        searchSourceBuilder.query(boolQueryBuilder);

        //5:排序 默认是按照综合排序 （热度） //1：desc  DESC Desc 或是 1:asc  或是2：desc ...
        String order = searchParam.getOrder();
        if(StringUtils.isNotEmpty(order)){
            String[] o = order.split(":");
            String fn = "";
            switch(o[0]){
                case "1":fn="hotScore";break;
                case "2":fn="price";break;
            }
            //equalsIgnoreCase忽略大小写 。 equals不忽略
            searchSourceBuilder.sort(fn, o[1].equalsIgnoreCase("asc")?SortOrder.ASC : SortOrder.DESC);
        }else {//没有排序的值就总默认
            searchSourceBuilder.sort("hotScore",SortOrder.DESC);
        }

        //6:分页（当前页、每页的条数） //当不设置分页时,默认是1页,默认显示10条数据
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        searchSourceBuilder.from((pageNo.intValue()-1)*pageSize.intValue());  // select * from 表  limit 开始行,每页数
        searchSourceBuilder.size(pageSize);

        //7.隐藏条件的高亮显示
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        //1)高亮的字段(哪个域)  //2)高亮的前缀 <font color='red'>手机</font> //3)高亮的后缀
        highlightBuilder.field("title").preTags("<font color='red'>").postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);

        //8.隐藏条件 分组设置  group by后面的字段
        //8.1 设置品牌分组  terms("tmIdAgg")可以随便设置，但取出来时要按照这个key取
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
                            .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                            .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));
        //8.2 设置平台属性分组
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrsAgg","attrs")
                            .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                            .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                            .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));
        //搜索请求对象
        SearchRequest searchRequest = new SearchRequest();
        //设置索引库的名称
        searchRequest.indices("goods");
        //设置索引库的类型(可有可无)不影响搜索。7.0版本以上取消了type
        searchRequest.types("info");
        //设置构建条件资源对象
        searchRequest.source(searchSourceBuilder);
        return searchRequest;
    }
    //构建返回值SearchResponseVo对象 4部分数据
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse){
        SearchResponseVo vo = new SearchResponseVo();
        SearchHits hits = searchResponse.getHits();
        //1.分页（总条数、总页数）
        long totalHits = hits.getTotalHits();
        System.out.println(totalHits);
        vo.setTotal(totalHits);
        //2.商品集合
        SearchHit[] hits1 = hits.getHits();
        List<Goods> goodsList = Arrays.stream(hits1).map(h -> {
            String sourceAsString = h.getSourceAsString();
            System.out.println(sourceAsString);
            Goods goods = JSONObject.parseObject(sourceAsString, Goods.class);
            //Goods对象的名称是否高亮显示
            HighlightField title = h.getHighlightFields().get("title");
            if(null != title){
                String tle = title.fragments()[0].toString();
                goods.setTitle(tle);
            }
            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);

        //3.构建条件的时候设置了品牌分组  解析品牌分组结果 getAggregations()获取分组集合  asMap():map集合
        //Aggregation接口无法解析结果，利用实现类ParsedLongTerms解析
        ParsedLongTerms tmIdAgg = (ParsedLongTerms)searchResponse.getAggregations().asMap().get("tmIdAgg");
        //getBuckets()拿到一个集合，利用stream流转换返回数据类型： List<SearchResponseTmVo>
        List<SearchResponseTmVo> responseTmVoList = tmIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            tmVo.setTmId(Long.parseLong(bucket.getKeyAsString()));//设置ID
            //设置品牌名称 它挂载在id的下面 getAggregations() 获取集合  id下的名称集合子分组 id固定，该值只有一个，直接get
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            //ParsedStringTerms字符串类型  该集合自由一个，直接get(0)
            tmVo.setTmName(tmNameAgg.getBuckets().get(0).getKeyAsString());
            //3)品牌的Logo图片
            ParsedStringTerms tmLogoUrlAgg = bucket.getAggregations().get("tmLogoUrlAgg");
            tmVo.setTmLogoUrl(tmLogoUrlAgg.getBuckets().get(0).getKeyAsString());
            return tmVo;
        }).collect(Collectors.toList());
        vo.setTrademarkList(responseTmVoList);

        //4.平台属性 多一个嵌套，ParsedNested嵌套对象
        ParsedNested attrsAgg = (ParsedNested)searchResponse.getAggregations().asMap().get("attrsAgg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> responseAttrValueList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //1)平台属性ID
            attrVo.setAttrId(Long.parseLong(bucket.getKeyAsString()));
            //2)平台属性名称
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            //3)平台属性值集合
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            //attrVo.setAttrValueList(attrValueAgg.getBuckets());泛型不匹配
            // List<? extends Terms.Bucket>:Terms.Bucket中有getKeyAsString()的属性是字符串类型
            //Terms.Bucket表示Terms包下的
            List<String> attrValueAggList = attrValueAgg.getBuckets()
                    .stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            attrVo.setAttrValueList(attrValueAggList);
            return attrVo;
        }).collect(Collectors.toList());
        vo.setAttrsList(responseAttrValueList);
        return vo;
    }

}
